package com.mixide.ui.part.memory;

import com.mixal.common.Utils;
import com.mixal.hardware.Memory;
import com.mixal.software.Byte;
import com.mixal.software.Field;
import com.mixal.software.Instruction;
import com.mixal.software.Instruction.Opcode;
import com.mixal.software.Word;
import com.mixal.software.Word.Sign;
import com.mixal.software.Words;

import lombok.Getter;

public class WordRow {

	private static final String OPERATION_NOP = "NOP";
	private final Memory memory;
	private final int index;
	private Word word;
	
	private @Getter String no;
	private @Getter String sign;
	private @Getter String byte1;
	private @Getter String byte2;
	private @Getter String byte3;
	private @Getter String byte4;
	private @Getter String byte5;
	private @Getter String operation;
	private @Getter String toChar;
	private @Getter String toInt;

	public WordRow(Memory memory, int index) {
		this.memory = memory;
		this.index = index;
		this.refresh();
	}
	
	public boolean isInstruction() {
		return this.word instanceof Instruction;
	}
	
	public void setSign(String sign) {
		Word word = null;
		if ("+".equals(sign)) {
			word = new Word(Sign.POSITIVE, this.word.bytes());
		} else {
			word = new Word(Sign.NEGATIVE, this.word.bytes());
		}
		this.changeWord(word);
	}

	public void setByte(int i, String bytei) {
		int bi = Integer.parseInt(bytei, 10);
		Byte[] bytes = this.word.bytes();
		bytes[i - 1] = new Byte(bi);
		Word word = new Word(this.word.sign(), bytes);
		this.changeWord(word);
	}
	
	public void setOperation(String operation) {
		int code = Opcode.valueOf(operation).code();
		Byte[] bytes = this.word.bytes();
		bytes[4] = new Byte(code);
		int address = bytes[0].toInt() * Utils.pow(Byte.RADIX, Byte.DIGITS) + bytes[1].toInt();
		Word word = new Instruction(address, bytes[2].toInt(), bytes[3].toInt(), Opcode.valueOf(operation));
		this.changeWord(word);
	}
	
	public void setToChar(String toChar) {
		String str = String.format("%1$5s", toChar);
		Word word = this.word.ofPart(Words.ofFiveChars(str), new Field(1, 5));
		this.changeWord(word);
	}
	
	public void setToInt(String toInt) {
		int num = Integer.parseInt(toInt, 10);
		Word word = new Word(num);
		this.changeWord(word);
	}
	
	private void changeWord(Word word) {
		if (this.word instanceof Instruction && !(word instanceof Instruction)) {
			word = this.instruction(word, ((Instruction) this.word).code());
		}
		this.memory.set(this.index, word);
		this.refresh();
	}
	
	private void refresh() {
		this.word = this.memory.get(this.index);
		
		this.no = String.format("%04d", this.index);
		this.sign = this.word.sign().sign();
		
		Byte[] bytes = this.word.bytes();
		this.byte1 = String.format("%02d", bytes[0].toInt());
		this.byte2 = String.format("%02d", bytes[1].toInt());
		this.byte3 = String.format("%02d", bytes[2].toInt());
		this.byte4 = String.format("%02d", bytes[3].toInt());
		this.byte5 = String.format("%02d", bytes[4].toInt());
		
		this.operation = OPERATION_NOP;
		if (this.word instanceof Instruction) {
			this.operation = ((Instruction) this.word).code().name();
		}
		
		this.toChar = Words.toString(this.word);
		this.toInt = String.valueOf(this.word.toInt());
	}
	
	private Instruction instruction(Word word, Opcode operation) {
		Byte[] bytes = word.bytes();
		int address = bytes[0].toInt() * Utils.pow(Byte.RADIX, Byte.DIGITS) + bytes[1].toInt();
		address *= word.sign().factor();
		return new Instruction(address, bytes[2].toInt(), bytes[3].toInt(), operation);
	}
}
